/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.path;

import edu.ucsd.db.bassarisk.exception.VisitorException;
import edu.ucsd.db.bassarisk.schema.AttributeClass;
import edu.ucsd.db.bassarisk.schema.DataSequenceClass;
import edu.ucsd.db.bassarisk.treepattern.DescentAxis;
import edu.ucsd.db.bassarisk.treepattern.EdgeFunction;

public abstract class DSDElements {
  public static class DSDAggregate extends DSDDescent {
    private final EdgeFunction function;

    public DSDAggregate(final DescentAxis axis,
                        final DataSequenceClass sequenceClass, final EdgeFunction f) {
      super(axis, sequenceClass);
      this.function = f;
    }

    @Override
    public <R, A> R accept(final DSDElementVisitor<R, A> visitor, final A args)
        throws VisitorException {
      return visitor.visitAggregate(this, args);
    }

    public EdgeFunction getFunction() {
      return this.function;
    }

    @Override
    public boolean isFilter() {
      return this.function == EdgeFunction.NonEmpty;
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder();
      this.toStringAxis(sb);
      sb.append("#").append(this.function.toString()).append(":")
          .append(this.getSequenceClass().getName());
      if (this.hasNext()) { 
        sb.append(this.getNext().toString());
      }
      return sb.toString();
    }
  }

  public static class DSDContext extends DSDElement {
    public DSDContext() {}

    @Override
    public <R, A> R accept(final DSDElementVisitor<R, A> visitor, final A args)
        throws VisitorException {
      return visitor.visitContext(this, args);
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder().append(".");
      if (this.hasNext()) { 
        sb.append(this.getNext().toString());
      }
      return sb.toString();
    }
  }

  public static class DSDDescent extends DSDElement {
    private final DescentAxis axis;
    private final boolean filter;
    private final DataSequenceClass sequenceClass;

    public DSDDescent(final DescentAxis axis, final DataSequenceClass sequenceClass) {
      this.axis = axis;
      this.sequenceClass = sequenceClass;
      this.filter = false;
    }

    public DSDDescent(final DescentAxis axis, final DataSequenceClass sequenceClass,
                      final boolean filter) {
      this.axis = axis;
      this.sequenceClass = sequenceClass;
      this.filter = filter;
    }

    @Override
    public <R, A> R accept(final DSDElementVisitor<R, A> visitor, final A args)
        throws VisitorException {
      return visitor.visitDescent(this, args);
    }

    public DescentAxis getAxis() {
      return this.axis;
    }

    public DataSequenceClass getSequenceClass() {
      return this.sequenceClass;
    }

    public boolean isFilter() {
      return this.filter;
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder();
      this.toStringAxis(sb);
      sb.append(this.isFilter() ? "∈:" : "=:");
      sb.append(this.getSequenceClass().getName());
      if (this.hasNext()) { 
        sb.append(this.getNext().toString());
      }
      return sb.toString();
    }

    protected void toStringAxis(final StringBuilder sb) {
      switch (this.getAxis()) {
      case Child:
        sb.append("/");
        break;
      case Descendant:
        sb.append("//");
        break;
      case Positions:
      default:
        sb.append("?");
      }
    }
  }

  public static abstract class DSDElement {
    private DSDElement next;

    public abstract <R, A> R accept(DSDElementVisitor<R, A> visitor, A args)
        throws VisitorException;

    public DSDElement getNext() {
      return this.next;
    }

    public boolean hasNext() {
      return this.next != null;
    }

    public void setNext(final DSDElement next) {
      this.next = next;
    }
  }

  public static class DSDFilter extends DSDElement {
    private final AttributeClass attributeClass;
    private final String filterValue;

    public DSDFilter(final AttributeClass attributeClass, final String filterValue) {
      this.attributeClass = attributeClass;
      this.filterValue = filterValue;
    }

    @Override
    public <R, A> R accept(final DSDElementVisitor<R, A> visitor, final A args)
        throws VisitorException {
      return visitor.visitFilter(this, args);
    }

    public AttributeClass getAttributeClass() {
      return this.attributeClass;
    }

    public String getFilterValue() {
      return this.filterValue;
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append("[@").append(this.attributeClass.getName()).append("=")
          .append(this.filterValue).append("]");
      if (this.hasNext()) { 
        sb.append(this.getNext().toString());
      }
      return sb.toString();
    }
  }
}